package net.aetherial.context.grounding.estimators;

import java.util.*;

import net.aetherial.context.grounding.estimators.loggers.RegressionLogger;

import org.rosuda.JRclient.RSrvException;
import org.rosuda.JRclient.Rconnection;

/**
 * An <code>AggregateEstimator</code> that learns from coded examples to generate a simple linear regression equation 
 * to generate estimates of other conversational units.
 */

public class RegressionEstimator extends AggregateEstimator
{
	private RegressionLogger regLogger;
	
	private Map<Estimator, ArrayList<Double>> observedValues;
	private List<Double> predictedValues;
	
	private Map<String, Double> coefficients;
	
	private Rconnection rConnection;

	/**
	 * @see net.aetherial.context.grounding.estimators.AggregateEstimator#reset()
	 */
	
	public void reset ()
	{
		for (String key: coefficients.keySet ())
		{
			System.out.println (key + ": " + coefficients.get (key));
		}

		super.reset ();

		observedValues = new HashMap<Estimator, ArrayList<Double>> ();
		predictedValues = new ArrayList<Double> ();
		coefficients = new HashMap<String, Double> ();
		
		regLogger.reset ();
	}

	/**
	 * Constructor.
	 */
	
	public RegressionEstimator ()
	{
		super ();
		
		observedValues = new HashMap<Estimator, ArrayList<Double>> ();
		predictedValues = new ArrayList<Double> ();
		coefficients = new HashMap<String, Double> ();

		try 
		{
			rConnection = new Rconnection ();
		}
		catch (RSrvException e) 
		{
			e.printStackTrace();
		}

		estimators.add (this);
		
		regLogger = new RegressionLogger ();
	}

	/**
	 * @see net.aetherial.context.grounding.estimators.AggregateEstimator#addEstimator(net.aetherial.context.grounding.estimators.Estimator)
	 */
	
	public void addEstimator (Estimator e)
	{
		super.addEstimator (e);

		observedValues.put (e, new ArrayList<Double> ());
	}

	/**
	 * Aggregates estimates from child estimators to generate a linear equation that describes a linear model associated
	 * with the children's predictions.
	 * 
	 * @param layers	Annotated conversational units
	 * @return			Aggregate prediction obtained by applying learned regression mode.
	 */
	
	public double estimate (Map<String, Map<String, Object>> layers) 
	{
		double score = super.estimate (layers);
		
		try 
		{
			HashMap<String, Double> scores = new HashMap<String, Double> ();

			for (Estimator e: estimators)
			{
				double estimate = 0.0;

				if (e == this)
					estimate = score;
				else
				{
					estimate = e.estimate (layers);
					scores.put (e.getName (), estimate);
				}
				
				if (score >= 0.0)
				{
					ArrayList<Double> dblList = observedValues.get (e);
					
					if (dblList == null)
					{
						dblList = new ArrayList<Double> ();
						observedValues.put (e, dblList);
					}

					dblList.add (estimate);
				}
			}
			
			double finalScore = 0.0;

			if (score > 0.0)
			{
				ArrayList<String> names = new ArrayList<String> ();
	
				for (Estimator e: estimators)
				{
					String name = e.getName ();
	
					if (e != this)
						names.add (name);
	
					ArrayList<Double> values = observedValues.get (e);
	
					double[] valueArray = new double[values.size ()];
	
					for (int i = 0; i < values.size (); i++)
						valueArray[i] = values.get (i);
	
					rConnection.assign (name, valueArray);
				}
	
				String expression = "eqn<-lm(" + this.getName () + "~";
	
				for (int i = 0; i < names.size (); i++)
				{
					expression += names.get (i);
	
					if (i != names.size () - 1)
						expression += "+";
				}
	
				expression += ")";
				
				// System.out.println (expression);
				rConnection.voidEval (expression);
	
				double [] coeff = rConnection.eval ("coefficients(eqn)").asDoubleArray ();
	
				finalScore = coeff[0];

				coefficients.put ("Intercept", coeff[0]);
				
				System.out.println ("Intercept: " + finalScore);
				
				// double [] ps = rConnection.eval ("anova(eqn)[,5]").asDoubleArray ();
	
				for (int i = 1; i < coeff.length; i++)
				{
					String name = names.get (i - 1);
					double estimate = scores.get (name);
	
					double contribution = estimate * coeff[i];
	
					System.out.println ("coeff["+ name + "] = " + coeff[i] + ", contribution = " + contribution);
	
					/* double p = 1.0;
					
					try
					{
						p = ps[i-1];
					}
					catch (ArrayIndexOutOfBoundsException e)
					{
						// e.printStackTrace ();
					} */
	
					// if (p <= 0.05)
					// 	finalScore += contribution;
					// else
					// 	System.out.println ("coeff["+ name + "] is not significant. p = " + p);

					if (contribution >= 0 || contribution < 0) // Catching NaN
					{
						coefficients.put (name, coeff[i]);
						finalScore += contribution;
					}
				}
			}
			
			System.out.println ("predicted value = " + finalScore + ", actual value = " + score);
			predictedValues.add (finalScore);
			
			regLogger.log (observedValues.get (this), predictedValues);
			return finalScore;
		} 
		catch (RSrvException e) 
		{
			e.printStackTrace();
		}

		return score;
	}

	/**
	 * Returns "RegressionEstimator".
	 * 
	 * @see net.aetherial.context.grounding.estimators.Estimator#getName()
	 */
	
	public String getName () 
	{
		return "RegressionEstimator";
	}

	/**
	 * @see net.aetherial.context.grounding.estimators.Estimator#estimate(java.lang.String, java.lang.String, double)
	 */
	
	public double estimate (String sender, String contribution, double score) 
	{
		return score;
	}
}
